#include <iostream>
#include <iomanip>
#include <string>
#include <fstream>
#include <map>
#include <stdio.h>
#include <stdexcept>
#include <sstream>
#include <cstring>
#include <sys/errno.h>
extern int errno;

using namespace std;

/*
 * A blob buff for input and output
 */
char lbuf[4096];
/**
 * Record the file position for each line. Read the begin position
 */
map<off_t, off_t> indexes;

/**
 * read one line from input buf
 *@return:
 * 	current position, a position integer.
 *  -1 if EOF is reached.
 *@throw logic_error upon I/O error
 */
off_t freadline(FILE* input) throw (logic_error);

/**
 * parse trhe input file.
 * go through each line. record the file position for the beginning each line.
 * 	line is counted from 0.
 * 	indexes[0] will be 0: the start position of line 0 will be 0
 *@postcondition: the file is set to beginning of the file
 */
void makeindexes(FILE* input) throw (logic_error);

/**
 * process the next patch from the diff file.
 */
void processNextPatch(istream& in, FILE* SRC, ostream& myout) throw (logic_error);

/**
 * copy the stream from startp to endp from in to out
 */
void copystream(FILE* in, off_t startp, off_t endp, ostream& out) throw (logic_error);


void dumpIndexes()
{
	map<off_t, off_t>::const_iterator iter=indexes.begin();
	for (; iter!=indexes.end(); ++iter)
	{
		cerr<<iter->first<<"="<<iter->second<<endl;
	} 	
}
	
off_t freadline(FILE* input) throw (logic_error)
{
	
	while(1)
	{
		int ret=fgetc(input);
		if (ret==EOF)
		{
			if (feof(input))
			{
				return -1;
			} else
			{
				string errMsg("file IO error2:");
				errMsg+=strerror(errno);
				throw logic_error(errMsg.c_str());			
			}
		} else
		{
			if (ret=='\n')
			{
				off_t retval=ftello(input);
				if (retval==-1)
				{

					string errMsg("file IO error1:");
					errMsg+=strerror(errno);
					throw logic_error(errMsg.c_str());		
				}else
					return retval;
			}
				
		} 
		
	}
}

void makeindexes(FILE* input) throw (logic_error)
{
	//place the file to beginning
	int status1=fseeko(input, 0L, SEEK_SET);
	if (status1<0)
	{
		string errMsg("fseeko error");
		errMsg+=strerror(errno);
		throw logic_error(errMsg.c_str());			
	}
	indexes[0]=0;

	for(int i=1; 1; i++)
	{
		off_t position=freadline(input);
		
		if (position ==-1)
		{
			//always record eof position
			indexes[i]=position;
			break;
		}else if (indexes.find(i)!=indexes.end())
		{
			indexes[i]=position;
		}
	}
	
	//replace the file to the beginning
	clearerr(input);
	status1=fseeko(input, 0L, SEEK_SET);
	if (status1<0)
	{
		string errMsg("fseeko error");
		errMsg+=strerror(errno);
		throw logic_error(errMsg.c_str());	
	}
	return;
}

/**
 * TODO add error handling
 */
void copystream(FILE* in, off_t startp, off_t endp, ostream& out) throw (logic_error)
{
	int status=fseeko(in, startp, SEEK_SET);
	if (status<0)
	{
		string errMsg("fseeko error");
					errMsg+=strerror(errno);
					throw logic_error(errMsg.c_str());	
	}
	if (endp==-1)
	{
		//copy the left file
		do
		{
			size_t len=fread(lbuf, 1, 4096, in);
			if(len!=4096 && !feof(in))
			{
				throw logic_error("expected 4096 bytes. But there is no so much");
			}
			 	//can we
			out.write(lbuf, len);
		}while(!feof(in));
	} else
	{
		int round=(endp-startp)/4096;
		for(int i=0; i<round; i++)
		{
			size_t len=fread(lbuf, 1, 4096, in);
			if(len!=4096)
			{
				throw logic_error("expected 4096 bytes. But there is no so much");
			}
			 	//can we
			out.write(lbuf, 4096);
		}
		int left=(endp-startp)%4096;
		size_t len=fread(lbuf, 1, left, in);
		if (len!=left)
		{
			throw logic_error("did not get expected byte");	
		}
		out.write(lbuf, left);
	}
}




void usage()
{
 cout<<"Usage:\n\tfpatch <patch file> <source file> <outfile>\n\n";
 exit(1);
}


void setUpIndexesKey(ifstream& diffin)
{
	string line;
	
	
	while (1) 
	{
		getline(diffin, line);
	
		if (diffin.eof())
		{
			diffin.seekg(0, ios_base::beg);
			diffin.clear();
			break;
		}
		string twoChars=line.substr(0, 2);
		if (twoChars == "#R")
		{
			istringstream  strs(line);
			string firsttoken;
			off_t beg, end;
			strs>>firsttoken>>beg>>end;
			indexes[beg-1]=-1;
			indexes[end]=-1;
		} else
		{
			//do nothing	
		}
	}	
	
}
	


int main( int argc, const char *argv[])
{

 // parse arguments
 	if( argc <4 ) usage();

 	string diffFile = argv[1];
 	string srcFile  = argv[2];
	string outfile = argv[3];
			      ofstream myout(argv[3]);
			      /*
	int seqs=1024*1024;
	if (argc==4)
	{
		seqs=atol(argv[3]);	
	}
			      */

	//user C++ IOS stream. We assume that the diff file is less than 2G
	ifstream diffin(argv[1]);
	if (!diffin)
	{
		cerr<<"can not open "<<	argv[1]<<endl;
		exit(1);
	}
	//diffin.exceptions(ios_base::badbit|ios_base::failbit);

	FILE* SRC=fopen(argv[2], "r");
	if (SRC==NULL)
	{
		cerr<<" can not open "<<argv[2]<<endl;	
		exit(1);
	}
	//TODO This could break the vector implemenattion
	// if the vertor does not support so many line
	try
	{
		setUpIndexesKey(diffin);
		diffin.close();
		ifstream diffin1(argv[1]);
		if (!diffin1)
		{
			cerr<<"can not open "<<	argv[1]<<endl;
			exit(1);
		}
		//dumpIndexes();
		makeindexes(SRC);
		//dumpIndexes();
		while(!diffin1.eof())
		{
		  processNextPatch(diffin1, SRC, myout);	
		}

	} catch (exception& error)
	{
		cerr<<error.what()<<endl;
		exit(1);	
	}
			      myout.close();
}


	  void processNextPatch(istream& in, FILE* SRC, ostream& myout) throw (logic_error)
{
	//if there is bad io, throw exception.
	in.exceptions(ios_base::badbit);
	string line;
	getline(in, line);
	
	//The last line must be a EOF alone.
	//This is enforced by fdiff
	if (in.eof())
	{
		return;	
	}
	istringstream  strs(line);
	string firsttoken;
	strs>>firsttoken;
	if (firsttoken=="#V0")
	{
		
	} else if (firsttoken=="#V1")
	{
		
		
	} else if (firsttoken=="#R")
	{
		off_t beg, end;
		strs>>beg>>end;
		off_t startp;
		off_t endp;
		map<off_t, off_t>::const_iterator iter;
		if((iter=indexes.find(beg-1))==indexes.end())
		{
			cerr<<"index for line "<<beg-1<<" is not found"<<endl;
			exit(1);
		}else
		{
			startp=iter->second;	
		}
		if((iter=indexes.find(end))==indexes.end())
		{
			cerr<<"index for line "<<end<<" is not found"<<endl;
			exit(1);
		}else
		{
			endp=iter->second;	
		}
		 copystream(SRC, startp, endp, myout);	
	}else if (firsttoken=="#A")
	{
		off_t beg, end;
		strs>>beg>>end;
		
   		for(int i=0; i<=beg; i++)
   		{
   			getline(in, line);
   			myout<<line<<"\n";
   		}
	}else
	{
		cerr<<"unknow pattern in diff file "<<endl;
		exit(2);	
	}
} 
